package com.aspire.nm.component.miniServer.protocol;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Vector;

import com.aspire.nm.component.util.DynamicLogger;
import org.apache.commons.lang3.StringUtils;

import com.aspire.nm.component.util.ConstantConfig;
import com.aspire.nm.component.miniServer.Server;
import com.aspire.nm.component.miniServer.protocol.exception.SocketUnavailableException;

public class Http11Process {

    private static final String logName = "Access";
    private static final DynamicLogger logger = new DynamicLogger(logName,"#PORT#");

    
    
    
    public static java.lang.ThreadLocal<String> clientIp = new ThreadLocal<String>(){ 
        @Override 
        protected String initialValue() { 
            return ""; 
        } 
    }; 
    
    
    
    private Server server;
    
    private Socket socket;
    private DataInputStream dataIn;
    private DataOutputStream dataOut;
    
    
    
    public Http11Process(Server server,Socket socket){
        this.server = server;
        this.socket = socket;
    }
    
    
    public void initSocket() throws IOException, SocketUnavailableException{
        
        if(socket.isClosed() || !socket.isConnected() 
                || socket.isInputShutdown() || socket.isOutputShutdown()){
            //高并发情况下,socket不能及时被处理,client 端socketTimeOut后可能会关闭相关资源
            throw new SocketUnavailableException();
        }
        /**
         * netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
         * netstat -n|grep 7070|wc -l
         * 
         * 
         * 修改/etc/sysctl.conf文件，服务器能够快速回收和重用那些TIME_WAIT的资源

#表示开启SYN Cookies。当出现SYN等待队列溢出时，启用cookies来处理，可防范少量SYN攻击，默认为0，表示关闭  
net.ipv4.tcp_syncookies = 1  
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接，默认为0，表示关闭  
net.ipv4.tcp_tw_reuse = 1  
#表示开启TCP连接中TIME-WAIT sockets的快速回收，默认为0，表示关闭  
net.ipv4.tcp_tw_recycle = 1
#表示如果套接字由本端要求关闭，这个参数决定了它保持在FIN-WAIT-2状态的时间  
net.ipv4.tcp_fin_timeout=30

生效命令
/sbin/sysctl -p
         */
        socket.setSoLinger(true, 10);
        socket.setTcpNoDelay(true);
        socket.setTrafficClass(0x04|0x10);
        socket.setSoTimeout(5000);
        
        dataIn = new DataInputStream(socket.getInputStream());
        dataOut=new DataOutputStream(socket.getOutputStream());
        
        clientIp.set(socket.getInetAddress().getHostAddress());
    }
    public void close()
    {
        server.statusInfo.close();
        try
        {
          if(dataOut != null){
              dataOut.close();
          }
          if(dataIn != null){
              dataIn.close();
          }
          if (socket != null){
            socket.close();
          }
          dataOut = null;
          dataIn = null;
          socket = null;
        }
        catch (IOException localIOException)
        {
            localIOException.printStackTrace(System.out);
        }
        socket = null;
      }
    
    
    
    
    public Request getReqest() throws IOException {
		Request request = new Request(server.config.getClientEncode());
		String requestContent = getRequestContentFromInputStream(dataIn);
		
		String [] params = requestContent.split("\r\n");
		for(String param:params){
			if(param.startsWith("GET")){
				if(param.indexOf("?") != -1){
					request.setPath(param.substring("GET /".length(), param.indexOf("?")));
					request.setParams(param.substring(param.indexOf("?")+1, param.indexOf(" HTTP/")));
				}else{
					request.setPath(param.substring("GET /".length(), param.indexOf(" HTTP/")));
				}
				request.setGet(true);
			}else if(param.startsWith("POST")){
				if(param.indexOf("?") != -1){
					request.setPath(param.substring("POST /".length(), param.indexOf("?")));
					request.setParams(param.substring(param.indexOf("?")+1, param.indexOf(" HTTP/")));
				}else{
					request.setPath(param.substring("POST /".length(), param.indexOf(" HTTP/")));
				}
				request.setGet(false);
			}else if(param.startsWith("Content-Length:")){
				request.setContentLength(Integer.parseInt(param.substring("Content-Length:".length()).trim()));
			}else{
				if(param.indexOf(":") != -1){
					String[] kv = param.split(":");
					request.getHeaders().put(kv[0], kv[1]);
				}
			}
		}
		request.setExtName();
		byte[] contentByte = new byte[request.getContentLength()];
		if(!request.isGet() && request.getContentLength() != 0){
			for(int i = 0;i < request.getContentLength();i++){
				contentByte[i] = (byte)dataIn.read();
			}
		}
		request.setPostdate(contentByte);
		if(server.config.getSessionExpireSec() != 0){
		    request.setSession(server.getPlugins().getSessionCacher());
		}
		String msg = requestContent+request.getPostdate();
		if(server.config.getAccessLogInLine() == 1){
		    logger.info(String.valueOf(server.config.getServerPort()),msg.replaceAll("\r\n", ","));
		}else{
		    logger.info(String.valueOf(server.config.getServerPort()),msg);
		}
		
		
		return request;
	}





	private byte[] HTTP_END_BYTES = new byte[]{13,10,13,10};
	private String getRequestContentFromInputStream(DataInputStream dataIn) throws IOException {
		Vector<Byte> v = new Vector<Byte>();
		byte [] byteEnd = new byte[4];
		int byteEndIndex = 0;
		while(true){
			byte b = (byte)dataIn.read();
			if(b == -1){
				throw new IOException("read -1");
			}
			v.add(b);
			if(byteEndIndex < 4){
				byteEnd[byteEndIndex++] = b;
			}else{
				byteEnd[0] = byteEnd[1];
				byteEnd[1] = byteEnd[2];
				byteEnd[2] = byteEnd[3];
				byteEnd[3] = b;
				if(byteEnd[0] == HTTP_END_BYTES[0] && byteEnd[1] == HTTP_END_BYTES[1] 
						&& byteEnd[2] == HTTP_END_BYTES[2] && byteEnd[3] == HTTP_END_BYTES[3]){
					break;
				}
			}
		}
		byte [] result = new byte[v.size()];
		int index = 0;
		for(byte b:v){
			result[index++] = b;
		}
		return new String(result);
	}

	
	
	
	
	
	private String customDefineHeaderString = null;
	private synchronized String getCustomDefineHeader(){
	    
	    if(customDefineHeaderString == null){
	        
	        if(!StringUtils.isEmpty(server.config.getHttpRespHeaderDefineFile())){
	            StringBuilder bld = new StringBuilder();
	            Iterator<Entry<Object, Object>> it = ConstantConfig.getProperties(server.config.getHttpRespHeaderDefineFile()).entrySet().iterator();
	            while (it.hasNext()) {
	                Entry<Object, Object> entry = it.next();  
	                Object key = entry.getKey();
	                if(key.equals("Content-Disposition") || key.equals("Content-Type") || key.equals("Set-Cookie") || key.equals("Cache-Control") ||
	                        key.equals("location") || key.equals("Content-Length") || key.equals("Content-Length") || key.equals("Connection")){
	                    continue;
	                }
	                Object value = entry.getValue();
	                bld.append(key+": "+value+"\r\n");
	                customDefineHeaderString = bld.toString();
	            }
	        }
	        
	        if(customDefineHeaderString == null) customDefineHeaderString = "";
	    }
	    
	    return customDefineHeaderString;
	}
	
	
	
	public void setResponse(Response response,Request request) throws IOException {
        StringBuilder bld = new StringBuilder();
        bld.append("HTTP/1.1 "+response.getHttpRespStatus()+" "+response.getHttpRespInfo()+"\r\n");
//      bld.append("Last-Modified: Sat, 10 Mar 2012 14:42:12 GMT\r\n");
//      bld.append("Accept-Ranges: bytes\r\n");
//      bld.append("Date: Sat, 10 Mar 2012 14:46:19 GMT\r\n");
//      bld.append("Expires: Sat, 10 Mar 1900 14:47:19 GMT\r\n");
        if(response.getContentDisposition() != null){
        	bld.append("Content-Disposition: "+ response.getContentDisposition() + "\r\n");
        }
        if(response.getContentType() != null){
            bld.append("Content-Type: "+response.getContentType()+"\r\n");
        }
        
        
        if(request != null && request.getJsessionid() != null){
            server.getPlugins().getSessionCacher().put(request.getJsessionid(), request.getSession(), server.config.getSessionExpireSec()*1000);
            bld.append("Set-Cookie:	JSESSIONID="+request.getJsessionid()+"; Path=/\r\n");
        }
        if(response.getIfcacherExpireTime() != 0){
            bld.append("Cache-Control: max-age="+(response.getIfcacherExpireTime() - System.currentTimeMillis())/1000+"\r\n");
        }else{
            bld.append("Cache-Control: no-cache\r\n");
        }
        
        if(response.getLocation() != null){
            bld.append("location: "+response.getLocation()+"\r\n");
        }
        if(response.getContentBytes() != null){
            bld.append(String.format("Content-Length: %d\r\n", response.getContentBytes().length));
        }else{
            bld.append(String.format("Content-Length: %d\r\n", 0));
        }
        bld.append("Connection: keep-alive\r\n");
        bld.append(getCustomDefineHeader() + "\r\n");
        dataOut.write(bld.toString().getBytes());
        if(response.getContentBytes() != null){
            dataOut.write(response.getContentBytes());
        }
        
        if(response.getContentBytes() == null){
            String msg = bld.toString();
            if(server.config.getAccessLogInLine() == 1){
                logger.info(String.valueOf(server.config.getServerPort()),msg.replaceAll("\r\n", ","));
            }else{
                logger.info(String.valueOf(server.config.getServerPort()),msg);
            }
        }else if(response.getContentType().startsWith("text/html") ||
        		response.getContentType().startsWith("text/plain") ||
        		response.getContentType().startsWith("text/xml") ||
        		response.getContentType().startsWith("text/vnd.wap.wml") ||
        		response.getContentType().startsWith("text/asp") ||
        		response.getContentType().startsWith("java/*") ){
            String msg = bld.toString()+new String(response.getContentBytes());
            if(server.config.getAccessLogInLine() == 1){
                logger.info(String.valueOf(server.config.getServerPort()),msg.replaceAll("\r\n", ","));
            }else{
                logger.info(String.valueOf(server.config.getServerPort()),msg);
            }
        }else{
            String msg = "(bytes.lenth = " + response.getContentBytes().length+")";
            if(server.config.getAccessLogInLine() == 1){
                logger.info(String.valueOf(server.config.getServerPort()),bld.toString() + msg.replaceAll("\r\n", ","));
            }else{
                logger.info(String.valueOf(server.config.getServerPort()),bld.toString() + msg);
            }
        }
        dataOut.flush();
	}
	
}
